#include "ksemantics.h"

#include "kglobal.h"
#include "ksymtbl.h" //SymbolTable
#include "klbltbl.h" //LabelTable
#include "ktypes.h"  //KType
#include "kobj.h"    //KObject::HigherType
#include "kopcodes.h"//calculateOpSize
#include "kerror.h"  //KCompileError

#include <stack>

void AnalyseSemantics(std::vector<Command> &cmds)
{
	std::vector<Command>::iterator it = cmds.begin();

	for(; it != cmds.end(); ++it)
	{
		// +5 for the line number information
		if (hasDebugInfo && it->type > LABEL_LINE)
			programSize += 5;

		switch (it->type)
		{
		case LABEL_LINE:
			lbltbl.AddLabel(it->identifier, programSize, line);
			// to be accessed later by emitting CTRL_GOTO et.al.
			break;
		case IO_OUT:
			DeduceExpressionType(*it, 0);
			break;
		case VAR_DECL:
		case VAR_DECL_EX:
			symtbl.AddSymbol(it->identifier, TranslateType(it->tokenList[0].type), line);
			break;
		case VAR_DEL:
			symtbl.RemoveSymbol(it->identifier, line);
			break;
		case STACK_PUSH:
			DeduceExpressionType(*it, 0);
			break;
		case TAPE_WRITE:
			DeduceExpressionType(*it, 1);
			break;
		case CTRL_GOTO_IF:
		case CTRL_CALL_IF:
			DeduceExpressionType(*it, 0);
			break;
		}

		PerformTypeCheck(*it);
		AccumulatePC(*it);
	}

	++programSize; // +1 for hlt
}

void PerformTypeCheck(Command &cmd)
{
	line = cmd.line;
	switch (cmd.type)
	{
	case IO_IN_VAR:
		// Saves the type for later use, side-effect: VarCheck
		cmd.tokenList.push_back(Token((k_int)symtbl.GetSymbolType(cmd.identifier, line)));
		break;
	case VAR_DECL_EX:
		switch (TypeMatchTable
			[symtbl.GetSymbolType(cmd.identifier, line)]
			[DeduceExpressionType(cmd, 1)])
		{
		case K_TYPE_MATCHED:
			// cvi/cvr/cvs unnecessary
			cmd.tokenList.push_back(Token((k_int)0));
			break;
		case K_TYPE_MISMATCHED:
			throw KCompileError(KCE_TYPE_MISMATCHED, line);
		case K_TYPE_CAST:
			// cvi/cvr/cvs needed
			cmd.tokenList.push_back(Token((k_int)1));
			break;
		}
		break;
	case VAR_SET:
		{
			// gets var's type, side-effect: VarCheck
			KType t = symtbl.GetSymbolType(cmd.identifier, line);
			// saves for later use
			cmd.tokenList.push_back(Token((k_int)t));
			switch (TypeMatchTable
				[t]
				[DeduceExpressionType(cmd, 0)])
			{
			case K_TYPE_MATCHED:
				// cvi/cvr/cvs unnecessary
				cmd.tokenList.push_back(Token((k_int)0));
				break;
			case K_TYPE_MISMATCHED:
				throw KCompileError(KCE_TYPE_MISMATCHED, line);
			case K_TYPE_CAST:
				// cvi/cvr/cvs needed
				cmd.tokenList.push_back(Token((k_int)1));
				break;
			}
		}
		break;
	case STACK_POP_VAR:
	case STACK_PEEK:
	case TAPE_READ_VAR:
		// Saves the var's type for later use, side-effect: VarCheck
		cmd.tokenList.push_back(Token((k_int)symtbl.GetSymbolType(cmd.identifier, line)));
		break;
	case TAPE_MOVE:
		switch (DeduceExpressionType(cmd, 0))
		{
		case K_INT:
			cmd.tokenList.push_back(Token((k_int)0));
			break;
		case K_REAL:
		case K_UNKNOWN:
			cmd.tokenList.push_back(Token((k_int)1));
			break;
		case K_STRING:
			throw KCompileError(KCE_TYPE_MISMATCHED, line);
		}
		break;
	}
}

KType DeduceExpressionType(const Command &cmd, int startIndex)
{
	std::stack<KType> tstack;
	KType a, b;

	std::vector<Token>::const_iterator it = cmd.tokenList.begin() + startIndex;
	for (; it->type != TOK_EOS; ++it)
	{
		switch (it->type)
		{
		case TOK_LITINT:
			tstack.push(K_INT);
			break;
		case TOK_LITREAL:
			tstack.push(K_REAL);
			break;
		case TOK_LITSTRING:
			tstack.push(K_STRING);
			// accompanying progress: add string to string pool
				strPool.AddString(it->v_string);
			break;
		case TOK_VAR:
			tstack.push(symtbl.GetSymbolType(it->v_string, line));
			break;
		case TOK_UPLUS:
		case TOK_UMINUS:
			if (tstack.top() == K_STRING)
				throw KCompileError(KCE_TYPE_MISMATCHED, line);
			break;
		case TOK_ADD:
		case TOK_SUB:
		case TOK_MUL:
		case TOK_DIV:
			b = tstack.top(); tstack.pop();
			a = tstack.top(); tstack.pop();
			a = KObject::HigherType(a, b);
			if (a == K_STRING)
				throw KCompileError(KCE_TYPE_MISMATCHED, line);
			else
				tstack.push(a);
			break;
		case TOK_IDIV:
		case TOK_MOD:
			b = tstack.top(); tstack.pop();
			a = tstack.top(); tstack.pop();
			if (KObject::HigherType(a, b) == K_STRING)
				throw KCompileError(KCE_TYPE_MISMATCHED, line);
			else
				tstack.push(K_INT);
			break;
		case TOK_POW: //code duplicated, but good for branch table
			b = tstack.top(); tstack.pop();
			a = tstack.top(); tstack.pop();
			a = KObject::HigherType(a, b);
			if (a == K_STRING)
				throw KCompileError(KCE_TYPE_MISMATCHED, line);
			else
				tstack.push(a);
			break;
		case TOK_EQU:
		case TOK_NEQ:
			tstack.pop(); tstack.pop();
			tstack.push(K_INT);
			break;
		case TOK_LT:
		case TOK_LTE:
		case TOK_GT:
		case TOK_GTE:
			b = tstack.top(); tstack.pop();
			a = tstack.top(); tstack.pop();
			if (KObject::HigherType(a, b) == K_STRING)
				throw KCompileError(KCE_TYPE_MISMATCHED, line);
			else
				tstack.push(K_INT);
			break;
		case TOK_CAT:
			tstack.pop(); tstack.pop();
			tstack.push(K_STRING);
			break;
		case TOK_AND:
		case TOK_OR:
		case TOK_XOR:
			tstack.pop(); tstack.pop();
			tstack.push(K_INT);
			break;
		case TOK_NOT:
			tstack.pop();
			tstack.push(K_INT);
			break;
		case TOK_CINT:
			tstack.pop();
			tstack.push(K_INT);
			break;
		case TOK_CREAL:
			tstack.pop();
			tstack.push(K_REAL);
			break;
		case TOK_FUNC:
			DeduceFunctionType(tstack, *it);
			break;
		}
	}

	return tstack.top();
}

inline void DeduceFunctionType(std::stack<KType> &tstack, const Token& t)
{
	switch (t.v_int)
	{
	case FUNC_abs:
		if (tstack.top() == K_STRING)
			throw KCompileError(KCE_TYPE_MISMATCHED, line);
		break;
	case FUNC_sqrt:
	case FUNC_sin:
	case FUNC_cos:
	case FUNC_tan:
	case FUNC_asin:
	case FUNC_acos:
	case FUNC_atan:
		if (tstack.top() == K_STRING)
			throw KCompileError(KCE_TYPE_MISMATCHED, line);
		tstack.pop();
		tstack.push(K_REAL);
		break;
	case FUNC_chr:
		if (tstack.top() == K_STRING)
			throw KCompileError(KCE_TYPE_MISMATCHED, line);
		tstack.pop();
		tstack.push(K_STRING);
		break;
	case FUNC_asc:
		tstack.pop();
		tstack.push(K_INT);
		break;
	case FUNC_left:
		{
			KType len = tstack.top(); tstack.pop();
			tstack.pop(); //str
			if (len == K_STRING)
				throw KCompileError(KCE_TYPE_MISMATCHED, line);
		}
		tstack.push(K_STRING);
		break;
	case FUNC_mid:
		{
			KType len = tstack.top(); tstack.pop();
			KType idx = tstack.top(); tstack.pop();
			tstack.pop(); //str
			if (len == K_STRING || idx == K_STRING)
				throw KCompileError(KCE_TYPE_MISMATCHED, line);
		}
		tstack.push(K_STRING);
		break;
	case FUNC_right:
		{
			KType len = tstack.top(); tstack.pop();
			tstack.pop(); //str
			if (len == K_STRING)
				throw KCompileError(KCE_TYPE_MISMATCHED, line);
		}
		tstack.push(K_STRING);
		break;
	case FUNC_iif:
		tstack.pop(); tstack.pop(); tstack.pop();
		tstack.push(K_UNKNOWN);
		break;
	case FUNC__tape:
	case FUNC__stack:
		{
			KType idx = tstack.top(); tstack.pop();
			if (idx == K_STRING)
				throw KCompileError(KCE_TYPE_MISMATCHED, line);
		}
		tstack.push(K_UNKNOWN);
		break;
	case FUNC__pop:
	case FUNC__peek:
		tstack.push(K_UNKNOWN);
		break;
	}
}

void AccumulatePC(const Command &cmd)
{
	switch (cmd.type)
	{
	case IO_IN_INT:
	case IO_IN_REAL:
		// in			1
		// cvin/cvrn	1
		// push			1
		programSize += 3;
		break;
	case IO_IN_STRING:
		// in			1
		// push			1
		programSize += 2;
		break;
	case IO_IN_VAR:
		// in				1
		// [ cvin/cvrn ]	1
		// stvar			5
		if (cmd.tokenList.begin()->v_int == (k_int)K_STRING)
			programSize += 6; // cvin/rn won't be used
		else
			programSize += 7; // cvin/rn will be used
		break;
	case IO_OUT:
		// ... (expression)
		// out
		{
			std::vector<Token>::const_iterator it = cmd.tokenList.begin();
			for (; it->type != TOK_EOS; ++it)
				programSize += calculateOpSize(it->type);
		}
		programSize += 1;
		break;
	case VAR_DECL:
		// ldi/ldr/ldes		5/9/1
		// new				5
		switch (cmd.tokenList.begin()->type)
		{
		case TOK_INT:
			programSize += 10;
			break;
		case TOK_REAL:
			programSize += 14;
			break;
		case TOK_STRING:
			programSize += 6;
			break;
		}
		break;
	case VAR_DECL_EX:
		// ... (expression)
		// [ cvi/cvr/cvs ]	1
		// new				5
		{
			std::vector<Token>::const_iterator it = cmd.tokenList.begin();
			for (; it->type != TOK_EOS; ++it)
				programSize += calculateOpSize(it->type);
		}
		// if cvi/cvr/cvs needed
		if (cmd.tokenList.back().v_int == 1)
			programSize += 1;
		programSize += 5;
		break;
	case VAR_DEL:
		// free		5
		programSize += 5;
		break;
	case VAR_SET:
		// ... (expression)
		// [ cvi/cvr/cvs ]	1
		// stvar			5
		{
			std::vector<Token>::const_iterator it = cmd.tokenList.begin();
			for (; it->type != TOK_EOS; ++it)
				programSize += calculateOpSize(it->type);
		}
		if (cmd.tokenList.back().v_int == 1)
			programSize += 1;
		programSize += 5;
		break;
	case STACK_PUSH:
		// ... (expression)
		// push		1
		{
			std::vector<Token>::const_iterator it = cmd.tokenList.begin();
			for (; it->type != TOK_EOS; ++it)
				programSize += calculateOpSize(it->type);
		}
		programSize += 1;
		break;
	case STACK_POP:
		// pop		1
		programSize += 1;
		break;
	case STACK_POP_VAR:
	case STACK_PEEK:
		// smv/stf		1
		// cvi/cvr/cvs	1
		// stvar		5
		programSize += 7;
		break;
	case STACK_SWAP:
	case STACK_CLEAR:
		// swap/sclr	1
		programSize += 1;
		break;
	case STACK_CLEAR_C:
		// sclrc	5
		programSize += 5;
		break;
	case TAPE_READ_INT:
	case TAPE_READ_REAL:
	case TAPE_READ_STRING:
		// trd 1 / trdx		5
		// cvi/cvr/cvs		1
		// push				1
		if (cmd.tokenList.begin()->v_int == -1)
			programSize += 1; //trd
		else
			programSize += 5; //trdx
		programSize += 2;
		break;
	case TAPE_READ_VAR:
		// trd 1 / trdx		5
		// [ cvi/cvr/cvs ]	1
		// stvar			5
		if (cmd.tokenList[0].v_int == -1)
			programSize += 1; //trd
		else
			programSize += 5; //trdx
		programSize += 6;
		break;
	case TAPE_WRITE:
		// ... (expression)
		// twr 1 / twrx		5
		{
			std::vector<Token>::const_iterator it = cmd.tokenList.begin() + 1;
			for (; it->type != TOK_EOS; ++it)
				programSize += calculateOpSize(it->type);
		}
		if (cmd.tokenList[0].v_int == -1)
			programSize += 1;
		else
			programSize += 5;
		break;
	case TAPE_NEXT:
	case TAPE_PREV:
		// tnxt/tprv 1
		programSize += 1;
		break;
	case TAPE_MOVE:
		// ... (expression)
		// [ cvi ]	1
		// tmov		1
		{
			std::vector<Token>::const_iterator it = cmd.tokenList.begin();
			for (; it->type != TOK_EOS; ++it)
				programSize += calculateOpSize(it->type);
		}
		if (cmd.tokenList.back().v_int == 1)
			programSize += 2; // cvi needed
		else
			programSize += 1;
		break;
	case CTRL_GOTO:
		// jmp	5
		programSize += 5;
		break;
	case CTRL_GOTO_IF:
		// ... (expression)
		// cjmp	5
		{
			std::vector<Token>::const_iterator it = cmd.tokenList.begin();
			for (; it->type != TOK_EOS; ++it)
				programSize += calculateOpSize(it->type);
		}
		programSize += 5;
		break;
	case CTRL_CALL:
		// call	5
		programSize += 5;
		break;
	case CTRL_CALL_IF:
		// ... (expression)
		// ccall	5
		{
			std::vector<Token>::const_iterator it = cmd.tokenList.begin();
			for (; it->type != TOK_EOS; ++it)
				programSize += calculateOpSize(it->type);
		}
		programSize += 5;
		break;
	case CTRL_RETURN:
	case CTRL_END:
		// ret / end	1
		programSize += 1;
		break;
	}
}
